<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>iOS远程真机</title>
    #include("/include/css.html")
    #include("/include/js.html")
    <style>
        .btn-left {
            display: inline-block;
            float: left;
            margin-left: 0.6em
        }
        .btn-right {
            display: inline-block;
            float: right;
            margin-right: 0.6em
        }
        .fileinput-button {
            position: relative;
            display: inline-block;
        }

        .fileinput-button input{
            position: absolute;
            right: 0px;
            top: 0px;
        }
        .fileinput-button {
            position: relative;
            display: inline-block;
            overflow: hidden;
        }
        .fileinput-button input{
            position: absolute;
            left: 0px;
            top: 0px;
            opacity: 0;
            -ms-filter: 'alpha(opacity=0)';
            font-size: 200px;
        }
        .canvas-container {
            margin:0 auto;
            width:360px;
        }
        .btn-home {
            padding-top: 5px;
            width:50px;
            height: 50px;
            margin: 0 auto
        }
        body, html {
            height: 100%;
            margin: 0;
            -webkit-font-smoothing: antialiased;
            font-weight: 100;
            background: #aadfeb;
            text-align: left;
            font-family: helvetica;
        }
        .tabs input[type=radio] {
            position: absolute;
            top: -9999px;
            left: -9999px;
        }
        .tabs {
            width: 650px;
            float: none;
            list-style: none;
            position: relative;
            padding: 0;
            margin: 5px auto;
        }
        .tabs li{
            float: left;
        }
        .tabs label {
            display: block;
            padding: 10px 20px;
            border-radius: 2px 2px 0 0;
            color: white;
            font-size: 24px;
            font-weight: normal;
            font-family: 'Roboto', helveti;
            background: rgba(73,163,97,0.9);
            cursor: pointer;
            position: relative;
            top: 3px;
            -webkit-transition: all 0.2s ease-in-out;
            -moz-transition: all 0.2s ease-in-out;
            -o-transition: all 0.2s ease-in-out;
            transition: all 0.2s ease-in-out;
        }
        .tabs label:hover {
            background: rgba(73,163,97,0.5);
            top: 0;
        }

        [id^=tab]:checked + label {
            background: rgba(73,163,97,1);
            color: white;
            top: 0;
        }

        [id^=tab]:checked ~ [id^=tab-content] {
            display: block;
        }
        .tab-content{
            z-index: 2;
            display: none;
            text-align: left;
            width: 100%;
            font-size: 20px;
            line-height: 140%;
            padding-top: 10px;
            background: white;
            padding: 15px;
            color: black;
            position: absolute;
            top: 53px;
            left: 0;
            box-sizing: border-box;
            -webkit-animation-duration: 0.5s;
            -o-animation-duration: 0.5s;
            -moz-animation-duration: 0.5s;
            animation-duration: 0.5s;
        }
    </style>
</head>
<body class="hold-transition skin-green-light layout-top-nav">
<div class="wrapper">
    #include("/include/header.html")
    <!-- Content Wrapper. Contains page content -->
    <div class="content-wrapper">
        <section class="content">
            <!--适配Air 13.3 -->
            <div class="col-sm-10 col-md-6">
                <div class="box box-success">
                    <div class="box-header">
                        <h4 style="margin-left: 0.6em;">
                            <span id="deviceTypeName" style="margin-left: 0.6em;">设备类型加载中...</span>
                            <span id="using" class="btn btn-danger btn-right" onclick="refreshTimer()">开始使用</span>
                            <span id="countTimer" class="btn-right" style="color:red; width:160px">已用时长：{{ record.duration|formatDuration }}</span>
                        </h4>
                    </div>
                    <span class="finger finger-0" style="transform: translate3d(0, 0, 0)"></span>
                    <span class="finger finger-1"></span>
                    <div class="canvas-container">
                        <canvas id="screen" width=360 height=640></canvas>
                    </div>
                    <div class="btn-home">
                        <span id="home" class="btn btn-success" onclick="home()">Home</span>
                    </div>
                </div>
            </div>
            <div class="col-sm-12 col-md-6">
                <!-- <div class="box box-success">-->
                <div class="box-body">
                    <div class="attachment-block">
                        <div class="form-group">
                            <div class="input-group">

                <span class="btn btn-success fileinput-button btn-left">
                     <span>上传应用</span>
                     <input id="uploadfile" type="file" name="uploadfile" onchange="upload()"/>
                    </span>
                            </div>
                        </div>

                        <div class="form-group">
                            <div class="input-group">
                                <input id="uninstallid" type="text" class="form-control" v-model="inputText"
                                       placeholder="请输入bundle id">
                                <span class="input-group-btn">
                    <button class="btn btn-success" type="button"
                            onclick="uninstallApp()">以BundleId卸载App</button>
                        </span>
                            </div>
                        </div>
                    </div>
                    <!--
                    <div class="form-group">
                      <div class="input-group">
                        <input type="file" name="uploadfile" id="uploadfile">
                        <button id="upload" type="button" onclick="return false;">上传并安装ipa</button>
                      </div>
                    </div> -->
                    <div class="form-group">
                        <div class="attachment-block">
                            <table id="deviceinfo" class="table">
                                <div class="btn btn-default" onclick="refreshdetail()">刷新信息</div>
                                <tbody>
                                <tr>
                                    <td>设备名称：</td>
                                    <td id="name" v-text="device.name"></td>
                                </tr>
                                <tr>
                                    <td>UDID：</td>
                                    <td id="udid" v-text="device.udid"></td>
                                </tr>
                                <tr>
                                    <td>系统版本：</td>
                                    <td id="osversion" v-text="device.version"></td>
                                </tr>
                                <tr>
                                    <td>已安装的App：</td>
                                    <td>
                                        <div id="apps" v-html="device.apps" style="width:465px; height: 92px;border: 1px #ccc solid; overflow-y: scroll;float: left;">
                                        </div>
                                    </td>
                                </tr>
                                </tbody>
                            </table>
                        </div>
                    </div>
                    <div class="main">
                        <ul class="tabs">
                            <li>
                                <input type="radio" name="tabs" id="tab1">
                                <label for="tab1">Log</label>
                                <div id="tab-content1" class="tab-content animated fadeIn">
                                    <div class="form-group">
                                        <div class="input-group">
                            <span class="input-group-btn">
                            <button id="getlogbtn" class="btn btn-success" onclick="refreshlogs()">开始日志抓取</button>
                            </span>
                                            <input id="filter-bundleid" type="text" value="com.apple.Preference"
                                                   class="form-control" v-model="inputText"
                                                   placeholder="请输入过滤日志的bundle id">

                                        </div>
                                    </div>
                                    <div class="form-group">
                                        <table>
                                            <tbody>
                                            <tr>
                                                <td>日志：</td>
                                                <td>
                                                    <div id="logs" v-html="device.logs"
                                                         style="width:465px; height: 120px;border: 1px #ccc solid; overflow-y:scroll; overflow-y:visible; overflow-x:scroll; float: left;">
                                                    </div>
                                                </td>
                                            </tr>
                                            </tbody>
                                        </table>
                                    </div>
                                </div>
                            </li>
                            <li>
                                <input type="radio" name="tabs" id="tab2">
                                <label for="tab2">截图</label>

                                <div id="tab-content2" class="tab-content animated fadeIn">
                                    <table>
                                        <tbody>
                                        <tr>
                                            <td>
                                                <span id="shot" class="btn btn-success btn-left">截图</span>
                                            </td>
                                            <td>
                                                <span id="save" class="btn btn-success btn-left">下载</span>
                                            </td>
                                        </tr>
                                        </tbody>
                                    </table>
                                    <div class="form-group">
                                        <div class="input-group">
                                            <div id="imgs"></div>
                                        </div>
                                    </div>


                                </div>
                            </li>
                            <li>
                                <input type="radio" name="tabs" id="tab3">
                                <label for="tab3">文本输入功能</label>
                                <div id="tab-content3" class="tab-content animated fadeIn">
                                    <div class="form-group">要使用sentText/clear功能，请在下方输入bundleId并启动App，无法跨app使用sendText.</div>
                                    <div class="form-group">
                                        <div class="input-group">
                                            <input id="bundleid" type="text" class="form-control" v-model="inputText"
                                                   placeholder="请输入bundle id">
                                            <span class="input-group-btn">
                            <button class="btn btn-success" type="button" onclick="launchApp()">以BundleId启动App</button>
                            </span>
                                        </div>
                                    </div>

                                    <div class="form-group">
                                        <div class="input-group">
                                            <input id="input" type="text" class="form-control" v-model="inputText"
                                                   placeholder="Input text ..."
                                                   @keyup.enter="sendText(inputText+'\n')">
                                            <span class="input-group-btn">
                              <button class="btn btn-default" type="button" onclick="sendText()">发送文本</button>
                              <button class="btn btn-default" type="button" onclick="clearText()">清空</button>
                            </span>
                                        </div>
                                    </div>
                                </div>
                            </li>
                        </ul>
                    </div>

                    <!--   </div>-->
                </div>
            </div>
        </section>
    </div>

</div>
<script src="/js/iosinfo.js"></script>
<script>
    var canvas, ctx, $shot,$save,$imgs;
    var imgType = 'png', imgW = 414, imgH = 736, imgX = 0, imgY = 0;
    function init () {
        canvas = document.getElementById('screen');
        ctx = canvas.getContext('2d');
        $shot = document.getElementById('shot');
        $save = document.getElementById('save');
        $imgs = document.getElementById('imgs');
        bind();
    }
    function bind () {

        $shot.onclick = function (e) {
            $imgs.appendChild(Canvas2Image.convertToImage(canvas, imgW/3, imgH/3, imgType));
        };
        $save.onclick = function (e) {
            var f = 'ShotSave';
            Canvas2Image.saveAsImage(canvas, imgW, imgH, imgType, f);
        }

    }
    onload = init;
</script>
<script>
    var WSURL2 = "ws://" + window.location.host + "/websocket2";
    var ws2 = null;

    var deviceLogsShow = new Vue({
        el: "#logs",
        data: {
            device: {
                logs: '',
            },
        },
    })

    var deviceDetail = new Vue({
        el: "#deviceinfo",
        data: {
            device: {
                name: '',
                udid: '',
                apps: '',
                logs: '',
                type: '',
                version: '',
            },
        },
        mounted: function () {
            this.refresh();
        },
        methods: {
            refresh: function () {
                console.log("getting device info...");
                var self = this;
                $.ajax({
                    url: "/ios/getDeviceInfo?",
                    method: "GET",
                })
                    .then(function (ret) {
                        self.device.udid = ret.udid
                        self.device.name = ret.name
                        self.device.apps = ret.apps
                        self.device.type = ret.type
                        self.device.version = ret.version
                        console.log(udid)
                        var typeString = self.device.type
                        var realTypeName = iosinfo[typeString]["iPhoneType"]
                        console.log(realTypeName)
                        document.getElementById('deviceTypeName').textContent = realTypeName
                        imgX = iosinfo[typeString]["ptX"]
                        imgY = iosinfo[typeString]["ptY"]
                    })
            },
        },
    });
    function refreshdetail() {
        console.log("getting device info...");
        var self = this;
        $.ajax({
            url: "/ios/getDeviceInfo?",
            method: "GET",
        })
            .then(function (ret) {
                deviceDetail.device.udid = ret.udid
                deviceDetail.device.name = ret.name
                deviceDetail.device.apps = ret.apps
                deviceDetail.device.type = ret.type
                deviceDetail.device.version = ret.version
                console.log(udid)
                var typeString = deviceDetail.device.type
                var realTypeName = iosinfo[typeString]["iPhoneType"]
                console.log(realTypeName)
                document.getElementById('deviceTypeName').textContent = realTypeName
                imgX = iosinfo[typeString]["ptX"]
                imgY = iosinfo[typeString]["ptY"]
            })
    }
    var countTimer = new Vue({
        el: "#countTimer",
        data: {
            record: {
                running: false,
                startTime: 0,
                ticker: null,
                duration: 0,
                prompt: ''
            }
        },
        filters: {
            formatDuration: function (value) {
                function pad2(number) {
                    return (number < 10 ? '0' : '') + number
                }
                var totalSeconds = Math.floor(value / 1000); //"00:00:00"
                var mms = value % 1000;
                var ms = totalSeconds % 60;
                var mm = (totalSeconds - ms) / 60
                return "" + pad2(mm) + " : " + pad2(ms);
            }
        },
        mounted: function () {
            var self = this;
            self.record.startTime = new Date().getTime();
            self.record.ticker = setInterval(function () {
                self.record.duration = new Date().getTime() - self.record.startTime;
            }.bind(self), 1000);
        }
    });
    function refreshlogs() {
        if (document.getElementById('getlogbtn').textContent == "开始日志抓取") {
            var id = document.getElementById('filter-bundleid').value
            if (id == "" || id.indexOf(".") == -1) {
                alert("请输入过滤日志的bundle id！")
                return
            }
            deviceLogsShow.device.logs = "";
            console.log("开始抓取日志...");
            var obj = document.getElementById('logs');
            ws2 = new WebSocket(WSURL2 + "/" + id)
            ws2.onclose = function () {
                console.log('ws2 onclose', arguments)
            }
            ws2.onerror = function () {
                console.log('ws2 onerror', arguments)
            }
            ws2.onmessage = function (message) {
                deviceLogsShow.device.logs += message.data;
                console.log('msg got:' + message.data);
                document.getElementById('logs').value += message.data
                //是否自动在底部滚动
                //obj.scrollTop =  obj.scrollHeight;
                var scrollTop = $("#logs")[0].scrollHeight;
                $("#logs").scrollTop(scrollTop);
            }
            ws2.onopen = function () {
                console.log('ws2 onopen', arguments)
                ws2.send(id);
            }
            document.getElementById('getlogbtn').textContent = "停止日志抓取"
        } else {
            ws2.close();
            console.log("停止抓取日志...");
            document.getElementById('getlogbtn').textContent = "开始日志抓取"
        }
    }
    function home() {
        $.ajax({
            url: "/ios/home",
            method: "GET",
        })
            .then(function (ret) {
                console.log(ret);
            })
    }
    function launchApp() {
        var id = document.getElementById('bundleid').value
        if (id != "") {
            $.ajax({
                url: "/ios/launchapp?id=" + id,
                method: "GET",
            })
                .then(function (ret) {
                    console.log(ret);
                })
        }
    }
    function uninstallApp() {
        var id = document.getElementById('uninstallid').value
        if (id != "") {
            $.ajax({
                url: "/ios/uninstall?id=" + id,
                method: "GET",
            })
                .then(function (ret) {
                    console.log(ret);
                })
        }
    }
    function getDeviceInfo() {
        var udid = "";
        $.ajax({
            url: "/ios/getDeviceInfo?",
            method: "GET",
        })
            .then(function (ret) {
                udid = ret.udid
                console.log(udid)
            })
    }
    function sendText() {
        var sendValue = encodeURI(encodeURI(document.getElementById('input').value))
        console.log("Input Text = " + sendValue)
        $.ajax({
            url: "/ios/inputText?value=" + sendValue,
            method: "GET",
        })
            .then(function (ret) {
                console.log(ret);
            })
    }
    function clearText() {
        $.ajax({
            url: "/ios/clear",
            method: "GET",
        })
            .then(function (ret) {
                console.log(ret);
            })
    }
</script>


<script>
    var app = new Vue({
        el: "#screen",
        // delimiters: ["[[", "]]"],
        data: {
            display: {
                width: 0,
                height: 0,
                naturalWidth: 0,
                naturalHeight: 0,
            },
            refreshCount: 0,
            sessionId: null,
            pageHidden: false,
            current: 0,
            inputText: '',
            wsAdmin: null,
            record: {
                running: false,
                startTime: 0,
                ticker: null,
                duration: 0,
                prompt: '',
            },
            records: [{
                name: "20170603-test",
            }]
        },
        filters: {
            formatDuration: function (value) {
                function pad2(number) {
                    return (number < 10 ? '0' : '') + number
                }
                var totalSeconds = Math.floor(value / 1000); //"00:00:00"
                var mms = value % 1000;
                var ms = totalSeconds % 60;
                var mm = (totalSeconds - ms) / 60
                return "" + pad2(mm) + " : " + pad2(ms);
            }
        },
        mounted: function () {
            var self = this;
            this.initTouchListener();
            this.initScreenSize();
            var self = this;
            $("#screen")[0].onload = function (e) {
                var nw = e.target.naturalWidth;
                var nh = e.target.naturalHeight;
                if (nw && nh && self.display.naturalHeight != nh) {
                    self.display.naturalWidth = nw;
                    self.display.naturalHeight = nh;
                    self.initScreenSize();
                    console.log("Detect screen rotate");
                }
            }.bind(this);
        },
        methods: {
            tap: function (x, y) {
                var self = this;
                return $.ajax({
                    url: "/ios/tap/?x=" + x + "&y=" + y,
                    method: "GET"
                }).then(function (ret) {
                    if (ret.status !== 0) {
                        console.log(ret.value);
                        return "Fail"
                    } else {
                        return "Success";
                    }
                })
            },
            tapHold: function (x, y, duration) {
                var self = this;
                duration = duration / 1000
                return $.ajax({
                    url: "/ios/tapHold?x=" + x + "&y=" + y + "&duration=" + duration,
                    method: "GET"
                }).then(function (ret) {
                    if (ret.status !== 0) {
                        console.log(ret.value);
                    } else {
                        return "Success";
                    }
                })
            },
            swipe: function (fromX, fromY, toX, toY) {
                var self = this;
                return $.ajax({
                    url: "/ios/swipe?fromX=" + fromX + "&fromY=" + fromY + "&toX=" + toX + "&toY=" + toY + "&duration=" + 0.08,
                    method: "GET"
                })
            },
            home: function () {
                return $.ajax({
                    url: "/ios/homeScreen",
                    method: "GET"
                }).then(function (ret) {
                    console.log(ret);
                })
            },
            sendText: function (text) {
                inputText = document.getElementById('input').value
                return $.ajax({
                    url: "/ios/inputText?value=" + inputText,
                    method: "GET"
                }).then(function (ret) {
                    console.log(ret);
                })
            },
            clearText: function (n) {
                this.sendText("\b".repeat(n || 30))
            },
            initScreenSize: function () {
                $.ajax({
                    url: "/status",
                })
                    .then(function (ret) {
                        this.sessionId = ret.sessionId;
                        Object.assign(this.device, ret.value.device);
                        return $.ajax({
                            url: "/session/" + ret.sessionId + "/window/size",
                        })
                    }.bind(this))
                    .then(function (ret) {
                        this.display.width = ret.value.width;
                        this.display.height = ret.value.height;
                    }.bind(this))
            },
            initTouchListener: function () {
                var self = this;
                var element = $("#screen")[0];
                var screen = {
                    bounds: {},
                    firstPosition: {},
                    secondPosition: {},
                    beginTime: null,
                }
                var _longClickTimeout = 800;
//                function calculateBounds() {
//                    var el = element;
//                    screen.bounds.w = el.offsetWidth
//                    screen.bounds.h = el.offsetHeight
//                    screen.bounds.x = 0
//                    screen.bounds.y = 0
//
//                    while (el.offsetParent) {
//                        screen.bounds.x += el.offsetLeft
//                        screen.bounds.y += el.offsetTop
//                        el = el.offsetParent
//                    }
//                }
                function deactiveFinger(index) {
                    $(".finger-" + index).removeClass("active").removeClass("longClick")
                }
                function convertPosition(event) {
                    var e = event;
                    if (e.originalEvent) {
                        e = e.originalEvent
                    }
                    // Ignore mouse right click
                    if (e.which === 3) {
                        return;
                    }
                    e.preventDefault()
//                    calculateBounds()
                    var x = e.offsetX - 30
                    var y = e.offsetY
                    console.log("page x: " + e.offsetX + ", page y: " + e.offsetY)
                    return {
                        e: e,
                        x: Math.floor(x / (element.clientWidth - 60) * imgX),
                        y: Math.floor(y / element.clientHeight * imgY),
                        pageX: x,
                        pageY: y,
                    }
                }
                function stopMousing() {
                    element.removeEventListener('mousemove', mouseMoveListener);
                    document.removeEventListener('mouseup', mouseUpListener);
                }
                function mouseUpListener(event) {
                    var p = convertPosition(event);
                    stopMousing()
                    var first = screen.firstPosition,
                        second = screen.secondPosition;
                    screen.secondPosition = {};
                    element.style.cursor = 'not-allowed'
                    clearTimeout(screen.longClickKey);
                    var defer;
                    if (second && (second.x || second.y)) {
                        console.log("Swipe", first, second);
                        defer = self.swipe(first.x, first.y, second.x, second.y);
                    } else {
                        var holdTime = new Date().getTime() - screen.beginTime;
                        if (holdTime > _longClickTimeout) {
                            console.log("LongTap", first);
                            defer = self.tapHold(first.x, first.y, holdTime)
                        } else {
                            console.log("Tap", first);
                            defer = self.tap(first.x, first.y)
                        }
                    }
                    defer.then(function () {
                        console.log("Release operation")
                        deactiveFinger(0);
                        deactiveFinger(1);
                        element.style.cursor = 'auto'
                    })
                }
                function mouseMoveListener(event) {
                    var p = convertPosition(event);
                    var first = screen.firstPosition;
                    var distance = Math.sqrt((first.x - p.x) * (first.x - p.x) + (first.y - p.y) * (first.y - p.y));
                    if (distance > 10) {
                        screen.secondPosition = {
                            x: p.x,
                            y: p.y
                        };
                        console.log(p);
                        $(".finger-1")
                            .addClass("active")
                            .css("transform", 'translate3d(' + p.pageX + 'px,' + p.pageY + 'px,0)')
                    } else {
                        deactiveFinger("1");
                        screen.secondPosition = {};
                    }
                }
                function mouseDownListener(event) {
                    if (element.style.cursor == 'not-allowed') {
                        return;
                    }
                    var p = convertPosition(event);
                    var index = 0;
                    $(".finger-" + index)
                        .addClass("active")
                        .css("transform", 'translate3d(' + p.pageX + 'px,' + p.pageY + 'px,0)')
                    screen.longClickKey = setTimeout(function () {
                        $(".finger-0").addClass("longClick");
                    }, _longClickTimeout)
                    screen.beginTime = new Date().getTime();
                    screen.firstPosition = {
                        x: p.x,
                        y: p.y
                    }
                    element.addEventListener('mousemove', mouseMoveListener);
                    document.addEventListener('mouseup', mouseUpListener);
                }
                element.addEventListener("mousedown", mouseDownListener);
            }
        }
    })
</script>

<script type="text/javascript">
    function upload() {
        var file = document.getElementById("uploadfile");
        var fileInfo = file.files[0];
        console.log(fileInfo);
        if (!fileInfo) {
            alert("请选择文件！");
            return false;
        }
        var fileName = fileInfo.name;
        //获取文件后缀名
        var file_typename = fileName.substring(
            fileName.lastIndexOf('.') + 1, fileName.length);
        if (file_typename.toLowerCase() != 'ipa') {
            alert("请上传.ipa类型的文件！");
            return false;
        }
        try {
            $.ajaxFileUpload({
                url: '/ios/upload',   //提交的路径
                type: 'post',
                secureuri: false, // 是否启用安全提交，默认为false
                fileElementId: 'uploadfile', // file控件id
                dataType: 'json',
                data: {},
                success: function (data, status) {
                    console.log(data);
                    console.log(status);
                },
                error: function (data, status) {
                    alert("上传失败");
                }
            });
        } catch (err) {
            alert("请上传.ipa文件，不要上传其它类型！" + err.message);
        }
    }
</script>

<script>
    var WSURL = "ws://" + window.location.host + "/websocket"
    /*jshint browser:true*/
    // BackFrame stores the latest frame retrieved from the socket. It does no
    // other work and can be swapped at any time.
    function BackFrame() {
        this.blob = null
    }
    BackFrame.prototype.swap = function (blob) {
        this.blob = blob
    }
    BackFrame.prototype.consume = function () {
        var blob = this.blob
        this.blob = null
        return blob
    }
    BackFrame.prototype.destroy = function () {
        this.consume()
    }
    // FrontFrame takes a blob from the BackFrame and loads it into an image.
    // There are two FrontFrames. One is always loading the next image and one
    // is always being used to render the latest fully loaded image. They're
    // swapped once loading completes and the process repeats.
    function FrontFrame(name) {
        this.name = name
        this.blob = null
        this.image = new Image()
        this.url = null
        this.loading = false
        this.loaded = false
        this.fresh = false
        this.onLoad = function () {
            this.loading = false
            this.loaded = true
            this.fresh = true
        }.bind(this)
        this.onError = function () {
            this.loading = false
            this.loaded = false
        }.bind(this)
    }
    FrontFrame.prototype.load = function (blob) {
        // If someone's calling load() they're already sure that they don't need
        // the the current frame anymore.
        this.reset()
        // Convenience check that must come after the reset.
        if (!blob) {
            return
        }
        this.blob = blob
        this.url = URL.createObjectURL(this.blob)
        this.loading = true
        this.loaded = false
        this.fresh = false
        this.image.onload = this.onLoad
        this.image.onerror = this.onError
        this.image.src = this.url
    }
    FrontFrame.prototype.reset = function () {
        this.loading = false
        this.loaded = false
        if (this.blob) {
            this.blob = null
            URL.revokeObjectURL(this.url)
            this.url = null
        }
    }
    FrontFrame.prototype.consume = function () {
        if (!this.fresh) {
            return null
        }
        this.fresh = false
        return this
    }
    FrontFrame.prototype.destroy = function () {
        this.reset()
        this.image = null
    }
    function Pipeline() {
        this.back = new BackFrame()
        this.mid = new FrontFrame('mid')
        this.front = new FrontFrame('front')
    }
    Pipeline.prototype.push = function (blob) {
        this.back.swap(blob)
        // If the mid frame isn't loading or waiting to be consumed, let's
        // ask it to load this new frame to speed up things.
        if (!this.mid.loading && !this.mid.loaded) {
            this.mid.load(this.back.consume())
        }
    }
    Pipeline.prototype.consume = function () {
        if (this.mid.loaded) {
            const mid = this.mid
            this.mid = this.front
            this.front = mid
            this.mid.load(this.back.consume())
        } else if (!this.mid.loading) {
            this.mid.load(this.back.consume())
        }
        return this.front.consume()
    }
    Pipeline.prototype.destroy = function () {
        this.back.destroy()
        this.mid.destroy()
        this.front.destroy()
    }
    // RenderLoop consumes and renders the pipeline.
    function RenderLoop(pipeline, canvas) {
        this.timer = null
        this.pipeline = pipeline
        this.canvas = canvas
        this.g = canvas.getContext('2d')
    }
    RenderLoop.prototype.start = function () {
        this.stop()
        this.next()
    }
    RenderLoop.prototype.stop = function () {
        cancelAnimationFrame(this.timer)
    }
    RenderLoop.prototype.next = function () {
        this.timer = requestAnimationFrame(this.run.bind(this))
    }
    RenderLoop.prototype.run = function () {
        var frame = this.pipeline.consume()
        if (frame) {
            this.canvas.width = frame.image.width
            this.canvas.height = frame.image.height
            this.g.drawImage(frame.image, 0, 0)
        }
        this.next()
    }
    var pipeline = new Pipeline()
    var canvas = document.getElementById('screen')
    var renderLoop = new RenderLoop(pipeline, canvas)
    var ws = new WebSocket(WSURL)
    ws.binaryType = 'blob'
    ws.onclose = function () {
        console.log('onclose', arguments)
        renderLoop.stop()
    }
    ws.onerror = function () {
        console.log('onerror', arguments)
        renderLoop.stop()
    }
    ws.onmessage = function (message) {
        //console.log('got jpeg', arguments)
        var blob = new Blob([message.data], {
            type: 'image/jpeg'
        })
        pipeline.push(blob)
    }
    ws.onopen = function () {
        console.log('onopen', arguments)
        ws.send('1920x1080/0')
        renderLoop.start()
    }
</script>

</body>
</html>